package com.vision.sales.sv.serv;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.sql.Timestamp;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;

import org.hibernate.SessionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.vision.core.cm.db.data.Company;
import com.vision.core.cm.db.data.CompanyGroup;
import com.vision.core.cm.util.DateUtils;
import com.vision.core.sv.db.CustomSessionFactoryBean;
import com.vision.core.sv.db.mgr.CompanyGroupManager;
import com.vision.core.sv.db.mgr.CompanyManager;
import com.vision.core.sv.util.CollectionUtils;
import com.vision.sales.cm.data.CollectionStatus;
import com.vision.sales.cm.data.PaymentTerm;
import com.vision.sales.cm.data.TransactionStatus;
import com.vision.sales.cm.db.data.Customer;
import com.vision.sales.cm.db.data.DeliveryReceipt;
import com.vision.sales.cm.db.data.Product;
import com.vision.sales.cm.db.data.SalesInvoice;
import com.vision.sales.cm.db.data.Salesman;
import com.vision.sales.cm.db.data.Salesman.SalesmanStatus;
import com.vision.sales.sv.db.mgr.CustomerManager;
import com.vision.sales.sv.db.mgr.DeliveryReceiptManager;
import com.vision.sales.sv.db.mgr.ProductManager;
import com.vision.sales.sv.db.mgr.SalesInvoiceManager;
import com.vision.sales.sv.db.mgr.SalesmanManager;

/**
 * 
 * @author Mark
 *
 */
@Service("sales_dummy_initializer")
public class DummyDataInitializerServlet extends HttpServlet {
	
	private static final long serialVersionUID = 1L;
	
	private static final Logger log = LoggerFactory.getLogger(DummyDataInitializerServlet.class);
	
	CompanyManager companyMgr = new CompanyManager();
	CompanyGroupManager companyGroupMgr = new CompanyGroupManager();
	
	ProductManager pMgr = new ProductManager();
	SalesmanManager sMgr = new SalesmanManager();
	CustomerManager cMgr = new CustomerManager();
	DeliveryReceiptManager drMgr = new DeliveryReceiptManager();
	SalesInvoiceManager siMgr = new SalesInvoiceManager();
	
	Map<String, Product> pm = new HashMap<String, Product>();
	Map<String, Salesman> sm = new HashMap<String, Salesman>();
	Map<String, Customer> cm = new HashMap<String, Customer>();
	
	@Override
	public void init() throws ServletException {
		super.init();
		
		SessionFactory sf = CustomSessionFactoryBean.getFactory();
		companyMgr.setSessionFactory(sf);
		companyGroupMgr.setSessionFactory(sf);
		
		pMgr.setSessionFactory(sf);
		sMgr.setSessionFactory(sf);
		cMgr.setSessionFactory(sf);
		
		drMgr.setSessionFactory(sf);
		siMgr.setSessionFactory(sf);
		
		if (!isInitialized()) {
			try {
				insertDummyData();
			} catch (Throwable e) {
				log.error(e.getMessage(), e);
				throw new RuntimeException(e);
			}
		}
	}
	
	boolean isInitialized() {
		List<Customer> customers = cMgr.getByIds("code", Arrays.asList(new String[]{ "GREENMILLS TRADING" }));
		return !customers.isEmpty();
	}
	
	List<String> readLines() throws IOException {
		InputStream is = DummyDataInitializerServlet.class.getResourceAsStream("dummy_data.txt");
		BufferedReader r = new BufferedReader(new InputStreamReader(is));
		List<String> lines = new ArrayList<String>();
		String line;
		
		while ((line = r.readLine()) != null) {
			lines.add(line.trim());
		}
		
		r.close();
		is.close();
		
		return lines;
	}
	
	void initMasterlists(List<String> lines) {
		String line = null;
		
		int i = 0;
		
		String[] groupCodes = new String[] {
				"ARTWRK", "PAPER", "INKS", "PLASTIC", "SHEETS", "PAINT"
		};
		
		for (; i < lines.size(); i++) {
			line = lines.get(i);
			if (line.equals("#Salesmen")) break;
			if (line.isEmpty() || line.equals("#Products")) continue;
			System.out.println(line);
			
			String[] split = line.split("\t");
			
			Product p = new Product();
			p.setGroup(groupCodes[ (int) (Math.random() * (groupCodes.length - 1)) ]);
			p.setCode(split[0]);
			p.setName(split[1]);
			
			p = pMgr.create(p);
			pm.put(p.getCode(), p);
		}
		
		for (; i < lines.size(); i++) {
			line = lines.get(i);
			if (line.equals("#Customers")) break;
			if (line.isEmpty() || line.equals("#Salesmen")) continue;
			System.out.println(line);
			
			String[] split = line.split("\t");
			
			Salesman s = new Salesman();
			s.setCode(split[0]);
			s.setName(split[1]);
			s.setStatus(SalesmanStatus.ACTIVE);
			
			s = sMgr.create(s);
			sm.put(s.getCode(), s);
		}
		
		for (; i < lines.size(); i++) {
			line = lines.get(i);
			if (line.isEmpty() || line.equals("#Customers")) continue;
			System.out.println(line);
			
			String[] split = line.split("\t");
			
			Customer c = new Customer();
			c.setCode(split[0]);
			c.setName(split[1]);
			c.setPaymentTerm(PaymentTerm.valueOf(split[2]));
			c.setSalesman(sm.get(split[3]));
			c.setWithholdingTax("T".equals(split[4]));
			c.setTaxRate(Double.valueOf(split[5]));
			c.setTaxRateDirect("T".equals(split[6]));
			c.setTin(split[7]);
			
			c = cMgr.create(c);
			cm.put(c.getCode(), c);
		}
	}
	
	@Transactional
	void insertDummyData() throws Throwable {
		try {
			List<String> lines = readLines();
			initMasterlists(lines);
			
			insertDeliveryReceipts();
			insertInvoices();
		} catch (Throwable e) {
			log.error(e.getMessage(), e);
			throw e;
		}
	}
	
	Map<String, DeliveryReceipt> drm = new LinkedHashMap<String, DeliveryReceipt>();
	Map<String, SalesInvoice> sim = new LinkedHashMap<String, SalesInvoice>();
	
	NumberFormat nf = new DecimalFormat("000000");
	
	void insertDeliveryReceipts() {
		Calendar cal = Calendar.getInstance();
		cal.set(Calendar.YEAR, 2010);
		cal.set(Calendar.MONTH, Calendar.JANUARY);
		cal.set(Calendar.DAY_OF_MONTH, 3);

		List<Customer> customers = new ArrayList<Customer>(cm.values());
		
		// Create delivery receipts
		for (int i = 0; i < 1500; i++) {
			int dayInc = (int) (Math.random() * 2);
			cal.add(Calendar.DAY_OF_MONTH, dayInc);
			
			DeliveryReceipt dr = new DeliveryReceipt(cal.getTime());
			dr.setDrNo("DR-" + nf.format(i));
			if (drm.get(dr.getDrNo()) != null) // Skip if duplicate to avoid DB error
				continue;
			
			dr.setCustomer(customers.get((int) (Math.random() * customers.size())));
			dr.setStatus(TransactionStatus.POSTED);
			
			dr.setSysCreated(new Timestamp(System.currentTimeMillis()));
			dr.setSysLastModified(dr.getSysCreated());
			
			dr = drMgr.create(dr);
			drm.put(dr.getDrNo(), dr);
		}
	}
	
	
	String getInvoiceNo(int number) {
		return nf.format(number);
	}
	
	String getPONo(int number) {
		return "PO-" + nf.format(number);
	}
	
	double[] AMOUNTS = { 10000, 15000, 20000, 25000, 30000, 35000, 40000, 45000, 50000, 55000, 
						60000, 65000, 70000, 75000, 80000, 85000, 90000, 95000, 100000, 120000, 140000,
						180000, 200000, 250000, 300000, 350000, 400000, 450000, 500000, 600000, 700000,
						800000, 900000, 950000 };
	
	static <T> T randomFromList(List<T> list) {
		return list.get( (int) (Math.random() * list.size()) );
	}
	
	void insertInvoices() {
		List<Company> companies = companyMgr.getAll();
		List<CompanyGroup> groups = companyGroupMgr.getAll();
		Map<String, List<CompanyGroup>> groupsByCompany = CollectionUtils.group(groups, "getCompanyCode");
		
		Calendar cal = Calendar.getInstance();
		cal.set(Calendar.YEAR, 2010);
		cal.set(Calendar.MONTH, Calendar.JANUARY);
		cal.set(Calendar.DAY_OF_MONTH, 3);

		List<Customer> customers = new ArrayList<Customer>(cm.values());
		List<Product> products = new ArrayList<Product>(pm.values());
		
		// Since drm is a LinkedHashMap, the order (chronological) in which receipts are inserted is maintained
		List<DeliveryReceipt> receipts = new ArrayList<DeliveryReceipt>(drm.values());
		
		// Create sales invoices
		for (int i = 0; i < 1000; i++) {
			int dayInc = (int) (Math.random() * 2) + 1;
			cal.add(Calendar.DAY_OF_MONTH, dayInc);
			
			Company someCompany = randomFromList(companies);
			List<CompanyGroup> cgs = groupsByCompany.get(someCompany.getCode()); 
			CompanyGroup cg = randomFromList(cgs);
			
			SalesInvoice si = new SalesInvoice(cal.getTime());
			si.setCompany(someCompany);
			si.setGroup(cg);
			si.setInvoiceNo(getInvoiceNo(i));
			if (sim.containsKey(si.getGroupAndNo())) // Skip if duplicate
				continue;
			
			si.setPoNo(getPONo(i));
			
			Customer c = randomFromList(customers);
			si.setCustomer(c);
			si.setSalesman(sm.get(c.getSalesman().getCode()));
			
			si.setGrossSales(AMOUNTS[ (int) (Math.random() * AMOUNTS.length)]);
			si.setBalance(si.getGrossSales());
			
			Product p = products.get((int) (Math.random() * products.size()));
			si.setProduct(p);
			si.setCommissionRate(10); // 10% FIXME Should get from salesman product table
			
			si.setCollectionStatus(CollectionStatus.UNPAID);
			si.setStatus(TransactionStatus.PENDING);
			
			si.setSysCreated(new Timestamp(System.currentTimeMillis()));
			si.setSysLastModified(si.getSysCreated());
			
			int noOfDrs = (int) (Math.random() * 3 + 1); // max of 3
			List<DeliveryReceipt> drInvoiced = new ArrayList<DeliveryReceipt>();
			for (int k = 0; k < receipts.size() && drInvoiced.size() < noOfDrs; k++) {
				DeliveryReceipt d = receipts.get(k);
				// Ensure same customer
				if (!d.getCustomer().equals(si.getCustomer()))
					continue;
				
				if (DateUtils.isAfter(d.getDate(), si.getDate()))
					break;
				
				drInvoiced.add(d);
				receipts.remove(k--);
			}
			
			if (!drInvoiced.isEmpty()) {
				si.setDeliveryReceipts(new HashSet<DeliveryReceipt>(drInvoiced));
			
				si = siMgr.create(si);
				sim.put(si.getGroupAndNo(), si);
				
				for (DeliveryReceipt d : drInvoiced) {
					d.setSalesInvoice(si);
					drMgr.update(d);
				}
			}
		}
	}

}
